<?php
/**
 * CreateTime: 2022/5/10 11:11
 */

namespace App\Tools\DingTalk;

use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\AddFileHeaders;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\AddFileRequest;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\GetUploadInfoHeaders;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\GetUploadInfoRequest;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\ListFilesHeaders;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\ListFilesRequest;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\ListSpacesHeaders;
use AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\ListSpacesRequest;
use AlibabaCloud\Tea\Exception\TeaError;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
use App\Consts\DingTalkConsts\DingTalkConfigConsts;
use App\Consts\ResponseConsts\ResponseStatusConsts;
use App\Func\AppFunc;
use App\Func\PathFunc;
use App\Func\ResponseFunc;
use App\Models\File\FileModel;
use App\Tools\DingTalk\DingTalkCover\DingTalkVDrive1_0CoverBasic;
use App\Tools\Oss\OssTools;
use CURLFile;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Storage;
use AlibabaCloud\Tea\Utils\Utils;
use \Exception;
use OapiFileUploadChunkRequest;

class DingTalkUploadFacade extends BaseDingTalk
{
    use NewDingTalkBasic,DingTalkVDrive1_0CoverBasic;

    private $m_gcHelper;

    public function __construct()
    {
        $this->m_gcHelper					= app()->gcHelper;
    }

    # 浏览器可能会转义某些字符导致请求失败，调试时请使用curl或者代码模拟请求。
    # 请保证自己的机器有足够的出口带宽，否则可能导致上传异常缓慢。
    #
    public function test(){
        dump(self::getAccessToken());
    }

    /**
     * 单步文件上传 文件大小不能超过 8M
     * @param $fileId "上传成功的文件id"
     */
    public function dingTalkUpload($fileId){
        // 通过 fileId 获取文件信息
        $fileModel = new FileModel();
        $fileInfo = $fileModel->getFileById($fileId);
        $fileSize = Storage::response($fileInfo['file_url'], $fileInfo['file_name'])->headers->get('content-length');
        $filePath = PathFunc::getStorageApp().$fileInfo['file_url'];
        dd($filePath);
        # 超过或者接近8M采用分块事务上传 避免文件上传失败
        if($fileSize == true){
            # 采用分块事务上传
            # 开启分块事务
            $transactionUploadRes = $this->transactionUpload($fileSize);
            if($transactionUploadRes['errcode']){
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "钉盘客户端错误:".$transactionUploadRes->errmsg,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            $transactionUploadId = $transactionUploadRes['upload_id'];
            # 文件分块
            $savePath = PathFunc::getStorageApp()."chunk";
            # 分块大小 8M
            $blockSize = 1024*1024*7;
            $chunkId = 1;
            $this->m_gcHelper->gfFileSpliter()
                ->setSourceFileFullPath($filePath)
                ->setFileMaxBlockSize($blockSize)
                ->setDestinationFileSaveDirectory($savePath)
                ->splitBigFileToSmallFile()
                ->forEeachAllSmallFile(function($one_file) use ($fileSize,&$chunkId,$transactionUploadId){
                    $this->chunkUpload($transactionUploadId,$one_file['file_path'],$chunkId);
                    $chunkId++;
            })->free();
            dd($this->commitTransactionUpload($fileSize,$transactionUploadId));
//            $chunkUploadRes = $this->chunkUpload($transactionUploadId,$filePath);
            dd();
            return 0;
        }
        # 采用单步文件上传
        $singleInfo = $this->singleUpload($filePath,$fileSize);
        if($singleInfo){
            $singleFormat = json_decode($singleInfo,true);
            if($singleFormat['errcode']){
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "钉盘客户端错误:".$singleFormat->errmsg,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
        return $singleFormat['media_id'];
        }
        throw new HttpResponseException(ResponseFunc::returnJson(
            'error',
            '',
            "文件上传错误",
            ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
        ));
    }

    private function singleUpload($filePath,$fileSize){
        $url = 'https://oapi.dingtalk.com/file/upload/single?agent_id=' . AppFunc::getDingTalkConfig()::DING_TALK_AGENT_ID . '&file_size=' . $fileSize . '&access_token=' . self::getAccessToken();

        return $this->extracted($url,array('file' => new CURLFILE($filePath)));
    }

    private function transactionUpload($fileSize){
        $curl = curl_init();
        # 计算分块大小
        $blockNum = 3;
        $url = 'https://oapi.dingtalk.com/file/upload/transaction?access_token='.self::getAccessToken().'&agent_id='.AppFunc::getDingTalkConfig()::DING_TALK_AGENT_ID.'&file_size='.$fileSize.'&chunk_numbers='.$blockNum;
        curl_setopt_array($curl, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'GET'
        ));

        $response = curl_exec($curl);
        curl_close($curl);
        return  json_decode($response,true);
    }

    private function commitTransactionUpload($fileSize,$uploadId){
        $curl = curl_init();
        # 计算分块大小
        $blockNum = 3;
        $url = 'https://oapi.dingtalk.com/file/upload/transaction?access_token='.self::getAccessToken().'&agent_id='.AppFunc::getDingTalkConfig()::DING_TALK_AGENT_ID.'&file_size='.$fileSize.'&chunk_numbers='.$blockNum.'&upload_id='.$uploadId;

        curl_setopt_array($curl, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'GET'
        ));

        $response = curl_exec($curl);
        dd($response);
        curl_close($curl);
        return  json_decode($response,true);
    }

    private function chunkUpload($uploadId,$filePath,$chunkId){
        $url = 'https://oapi.dingtalk.com/file/upload/chunk?'.
                'agent_id='.urlencode(AppFunc::getDingTalkConfig()::DING_TALK_AGENT_ID).
                '&chunk_sequence='.urlencode($chunkId).
                '&upload_id='.urlencode($uploadId);
        $request = new OapiFileUploadChunkRequest();
        $request->setFile(new CURLFILE($filePath));
        $res = self::getDingTalkOldClient()->execute($request,self::getAccessToken(),$url);
        dd($res);
//        return $this->extracted($url,array('file' => new CURLFILE($filePath)));
    }


    private function extracted($url,$postData): string|bool
    {
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL =>  $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_POSTFIELDS =>$postData,
        ));
        $response = curl_exec($curl);
        dd($response);
        curl_close($curl);
        return $response;
    }

    /**
     * 通过UnionId获取钉盘空间信息
     * @param $unionId
     * @param int $maxResults
     * @param string $nextToken
     * @return \AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\ListSpacesResponseBody|TeaError|Exception|void
     */
    public function getSpacesListByUnionId($unionId,$maxResults = 50,$nextToken = ''){
        $client = self::getDrive1_0ClientClient();
        $listSpacesHeaders = new ListSpacesHeaders([]);
        $listSpacesHeaders->xAcsDingtalkAccessToken = self::getAccessToken();
        $params = [
            "spaceType" => "org",
            "maxResults" => $maxResults,
            "unionId" => $unionId
        ];
        if($nextToken){
            $params['nextToken'] = $nextToken;
        }
        $listSpacesRequest = new ListSpacesRequest($params);
        try {
            $spacesList = $client->listSpacesWithOptions($listSpacesRequest, $listSpacesHeaders, new RuntimeOptions([]));
            return $spacesList->body;
        }
        catch (Exception $err) {
            if (!($err instanceof TeaError)) {
                $err = new TeaError([], $err->getMessage(), $err->getCode(), $err);
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "180".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            if (!Utils::empty_($err->code) && !Utils::empty_($err->message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "189".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
        }

    }

    /**
     * 创建钉钉文件夹
     * @param $spaceName
     * @param $createUnionId
     * @param $spaceId
     */
    public function createSpaceBySpaceName($spaceName,$createUnionId,$spaceId,$parentId = 0){
        $client = self::getDrive1_0ClientClient();
        $addFileHeaders = new AddFileHeaders([]);
        $addFileHeaders->xAcsDingtalkAccessToken = self::getAccessToken();
        $addFileRequest = new AddFileRequest([
            "fileName" => $spaceName,
            "unionId" => $createUnionId,
            "parentId" => $parentId,
            "fileType" => "folder",
            "addConflictPolicy"=>"returnExisting"
        ]);
        try {
           $createFolder =  $client->addFileWithOptions($spaceId, $addFileRequest, $addFileHeaders, new RuntimeOptions([]));
           return $createFolder->body;
        }
        catch (Exception $err) {
            if (!($err instanceof TeaError)) {
                $err = new TeaError([], $err->getMessage(), $err->getCode(), $err);
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "216".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            if (!Utils::empty_($err->code) && !Utils::empty_($err->message)) {
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "224" . $err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
        }
    }

    /**
     * 上传文件到指定空间
     *
     * @param $fileName '文件名.文件后缀'
     * @param $createUnionId '用户 unionId 通过 getDingTalkUserInfoByUserId() 获取'
     * @param $spaceId '空间ID'
     * @param $mediaId '文件上传 Object Key 通过 dingTalkUpload() 获取'
     * @param $parentId '父文件ID'
     */
    public function addFileToDingTalkByFileName($fileName,$createUnionId,$spaceId,$mediaId,$parentId){
        $client = self::getDrive1_0ClientClient();
        $addFileHeaders = new AddFileHeaders([]);
        $addFileHeaders->xAcsDingtalkAccessToken = self::getAccessToken();
        $addFileRequest = new AddFileRequest([
            "parentId" => $parentId,
            "fileType" => "file",
            "fileName" => $fileName,
            "mediaId" => $mediaId,
            "addConflictPolicy" => "autoRename",
            "unionId" => $createUnionId
        ]);
        try {
            $res = $client->addFileWithOptions($spaceId, $addFileRequest, $addFileHeaders, new RuntimeOptions([]));
            return $res->body;
        }
        catch (Exception $err) {
            if (!($err instanceof TeaError)) {
                $err = new TeaError([], $err->getMessage(), $err->getCode(), $err);
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "271".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            if (!Utils::empty_($err->code) && !Utils::empty_($err->message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "280".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }

        }
    }

    /**
     * @param $parentId
     * @param $unionId
     * @param $spaceId
     * @param int $maxResults 分页大小
     * @param string $nextToken 下一页脚标
     */
    public function getFolderOrFileList($parentId,$unionId,$spaceId,$maxResults = 50,$nextToken = ''){
        $client = self::getDrive1_0ClientClient();
        $listFilesHeaders = new ListFilesHeaders([]);
        $listFilesHeaders->xAcsDingtalkAccessToken = self::getAccessToken();
        $params = [
            "unionId" => $unionId,
            "parentId" => $parentId,
            "maxResults" => $maxResults
        ];
        if($nextToken){
            $params['nextToken'] = $nextToken;
        }
        $listFilesRequest = new ListFilesRequest($params);
        try {
            $list = $client->listFilesWithOptions($spaceId, $listFilesRequest, $listFilesHeaders, new RuntimeOptions([]));
            return $list->body;
        }
        catch (Exception $err) {
            if (!($err instanceof TeaError)) {
                $err = new TeaError([], $err->getMessage(), $err->getCode(), $err);
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "318".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            if (!Utils::empty_($err->code) && !Utils::empty_($err->message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "324".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
        }
    }

    /**
     * 获取文件上传临时凭证
     * @param $fileName
     * @param $parentId
     * @param $spaceId
     * @param $unionId
     * @return \AlibabaCloud\SDK\Dingtalk\Vdrive_1_0\Models\GetUploadInfoResponseBody|void
     */
    public function getUploadInfoHeaders($fileName,$fileSize,$parentId,$spaceId,$unionId){
        $client = self::getDrive1_0ClientClient();
        $getUploadInfoHeaders = new GetUploadInfoHeaders([]);
        $getUploadInfoHeaders->xAcsDingtalkAccessToken = self::getAccessToken();
        $getUploadInfoRequest = new GetUploadInfoRequest([
            "unionId" => $unionId,
            "fileName" => $fileName,
            "fileSize" => $fileSize,
            "md5" => md5($fileName),
            "addConflictPolicy" => "autoRename"
        ]);
        try {
           $res = $client->getUploadInfoWithOptions($spaceId, $parentId, $getUploadInfoRequest, $getUploadInfoHeaders, new RuntimeOptions([]));
           return $res->body->stsUploadInfo;
        }
        catch (Exception $err) {
            if (!($err instanceof TeaError)) {
                $err = new TeaError([], $err->getMessage(), $err->getCode(), $err);
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "409".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
            if (!Utils::empty_($err->code) && !Utils::empty_($err->message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
                throw new HttpResponseException(ResponseFunc::returnJson(
                    'error',
                    '',
                    "414".$err->message,
                    ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
                ));
            }
        }
    }

    /**
     * 上传文件至钉钉临时 OSS
     * @param $fileName '文件名 该文件名不影响实际文件名'
     * @param $filePath '文件地址 本机地址'
     * @param $fileSize '文件大小'
     * @param $parentId '父ID'
     * @param $spaceId  '钉盘空间ID'
     * @param $unionId  '用户唯一ID'
     * @return mixed
     */
    public function uploadDingPanByOss($fileName,$filePath,$fileSize,$parentId,$spaceId,$unionId): mixed
    {
        # 获取临时文件上传
        $ossInfo = self::getUploadInfoHeaders($fileName,$fileSize,$parentId,$spaceId,$unionId);
        $result = OssTools::OSSUpload(
            securityToken: $ossInfo->accessToken,
            accessKeyId: $ossInfo->accessKeyId,
            accessKeySecret: $ossInfo->accessKeySecret,
            endPoint: $ossInfo->endPoint,
            bucket: $ossInfo->bucket,
            ossKey: $ossInfo->mediaId,
            filePath:file_get_contents($filePath)
        );
        if(!$result){
            throw new HttpResponseException(ResponseFunc::returnJson(
                'error',
                '',
                "440 上传至钉盘失败",
                ResponseStatusConsts::INTERNAL_SERVER_ERROR_500_RESPONSE_STATUS
            ));
        }
        return $ossInfo->mediaId;
    }

}